package com.atguigu.gmall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.rabbit.config.MqConst;
import com.atguigu.gmall.common.rabbit.util.RabbitService;
import com.atguigu.gmall.product.mapper.SkuAttrValueMapper;
import com.atguigu.gmall.product.mapper.SkuSaleAttrValueMapper;
import com.atguigu.gmall.product.mapper.SpuSaleAttrMapper;
import com.atguigu.gmall.product.model.*;
import com.atguigu.gmall.product.redis.GmallCache;
import com.atguigu.gmall.product.service.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author: atguigu
 * @create: 2023-06-09 10:37
 */
@Slf4j
@Service
public class SkuManageServiceImpl implements SkuManageService {

    @Autowired
    private SkuInfoService skuInfoService;

    @Autowired
    private SkuImageService skuImageService;

    @Autowired
    private SkuAttrValueService skuAttrValueService;

    @Autowired
    private SkuSaleAttrValueService skuSaleAttrValueService;

    @Autowired
    private SpuImageService spuImageService;

    @Autowired
    private SpuSaleAttrService spuSaleAttrService;


    @Autowired
    private RedisTemplate redisTemplate;


    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RabbitService rabbitService;

    /**
     * 查询Spu包含所有商品图片列表
     *
     * @param spuId
     * @return
     */
    @Override
    public List<SpuImage> getSpuImageList(Long spuId) {
        LambdaQueryWrapper<SpuImage> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SpuImage::getSpuId, spuId);
        queryWrapper.select(SpuImage::getId, SpuImage::getSpuId, SpuImage::getImgName, SpuImage::getImgUrl);
        return spuImageService.list(queryWrapper);
    }


    /**
     * 查询指定SPU商品包含销售属性列表(每个销售属性包含销售属性值列表)
     *
     * @param spuId
     * @return
     */
    @Override
    public List<SpuSaleAttr> getSpuSaleAttrList(Long spuId) {
        //1.获取商品SPu销售属性持久层Mapper对象
        SpuSaleAttrMapper spuSaleAttrMapper = (SpuSaleAttrMapper) spuSaleAttrService.getBaseMapper();
        //2.调用持久层Mapper对象自定义方法-动态SQL调用
        return spuSaleAttrMapper.getSpuSaleAttrList(spuId);
    }


    /**
     * 保存商品SKU
     * 1.将新增SKU页面提交SKU基本信息封装到SkuInfo对象 保存到sku_info表
     * 2.将新增SKU页面选择图片列表封装SkuImage对象集合中 批量保存到sku_image表
     * 3.将新增SKU页面选择平台属性封装SkuAttrValue对象集合中 批量保存sku_attr_value表
     * 4.将新增SKU页面选择销售属性封装SkuSaleAttrValue对象集合中 批量保存sku_sale_attr_value表
     *
     * @param skuInfo
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)  //默认发生RuntimeException会进行事务回滚
    public void saveSkuInfo(SkuInfo skuInfo) {
        //1.将新增SKU页面提交SKU基本信息封装到SkuInfo对象 保存到sku_info表
        skuInfoService.save(skuInfo);
        Long skuId = skuInfo.getId();
        //2.将新增SKU页面选择图片列表封装SkuImage对象集合中 批量保存到sku_image表
        List<SkuImage> skuImageList = skuInfo.getSkuImageList();
        if (!CollectionUtils.isEmpty(skuImageList)) {
            skuImageList.stream().forEach(skuImage -> {
                //2.1 将sku图片跟sku关联
                skuImage.setSkuId(skuId);
            });
            //2.2 批量保存商品图片
            skuImageService.saveBatch(skuImageList);
        }
        //3.将新增SKU页面选择平台属性封装SkuAttrValue对象集合中 批量保存sku_attr_value表
        List<SkuAttrValue> skuAttrValueList = skuInfo.getSkuAttrValueList();
        if (!CollectionUtils.isEmpty(skuAttrValueList)) {
            skuAttrValueList.stream().forEach(skuAttrValue -> {
                //3.1 将平台属性关联到Sku
                skuAttrValue.setSkuId(skuId);
            });
            //3.2 批量保存平台属性列表
            skuAttrValueService.saveBatch(skuAttrValueList);
        }
        //4.将新增SKU页面选择销售属性封装SkuSaleAttrValue对象集合中 批量保存sku_sale_attr_value表
        List<SkuSaleAttrValue> skuSaleAttrValueList = skuInfo.getSkuSaleAttrValueList();
        if (!CollectionUtils.isEmpty(skuSaleAttrValueList)) {
            skuSaleAttrValueList.stream().forEach(skuSaleAttrValue -> {
                //4.1 关联商品SPU
                skuSaleAttrValue.setSpuId(skuInfo.getSpuId());
                //4.2 关联商品SKU
                skuSaleAttrValue.setSkuId(skuId);
            });
            //4.3 批量保存销售属性
            skuSaleAttrValueService.saveBatch(skuSaleAttrValueList);
        }
        //5. 将保存商品后商品ID保存到布隆过滤器
        RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(RedisConst.SKU_BLOOM_FILTER);
        bloomFilter.add(skuId);
    }

    /**
     * 分页查询商品SKU列表
     *
     * @param page        分页对象
     * @param category3Id 分类ID
     * @return
     */
    @Override
    public Page<SkuInfo> getSkuByPage(Page<SkuInfo> page, Long category3Id) {
        LambdaQueryWrapper<SkuInfo> queryWrapper = new LambdaQueryWrapper<>();
        if (category3Id != null) {
            queryWrapper.eq(SkuInfo::getCategory3Id, category3Id);
        }
        queryWrapper.orderByDesc(SkuInfo::getUpdateTime);
        return skuInfoService.page(page, queryWrapper);
    }

    /**
     * 商品SKU上架
     *
     * @param skuId
     * @return
     */
    @Override
    public void onSale(Long skuId) {
        SkuInfo skuInfo = new SkuInfo();
        skuInfo.setId(skuId);
        skuInfo.setIsSale(1);
        skuInfoService.updateById(skuInfo);

        //基于MQ异步通知搜索服务删除商品文档
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS, MqConst.ROUTING_GOODS_UPPER, skuId);
    }

    /**
     * 商品SKU下架
     *
     * @param skuId
     * @return
     */
    @Override
    public void cancelSale(Long skuId) {
        try {
            //保证缓存跟数据库一致性-采用延迟双删
            //0. 先清除缓存中业务数据
            String key = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
            redisTemplate.delete(key);
            //1.创建更新条件对象 update sku_info set is_sale = 0 where id = 24
            LambdaUpdateWrapper<SkuInfo> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.set(SkuInfo::getIsSale, 0);
            updateWrapper.eq(SkuInfo::getId, skuId);
            //2.执行更新
            skuInfoService.update(updateWrapper);

            //3.睡眠一段 500ms
            TimeUnit.MILLISECONDS.sleep(500);

            //4.再次清除缓存中数据
            redisTemplate.delete(key);

            //5.基于MQ异步通知搜索服务删除商品文档
            rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS, MqConst.ROUTING_GOODS_LOWER, skuId);
        } catch (InterruptedException e) {
            log.error("[商品服务]修改商品异常:{}", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 模拟修改商品信息方法 保证数据库缓存一致性
     *
     * @param skuInfo
     */
    public void updateSkuInfo(SkuInfo skuInfo) {
        try {
            Long skuId = skuInfo.getId();
            //保证缓存跟数据库一致性-采用延迟双删
            //0. 先清除缓存中业务数据
            String key = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
            redisTemplate.delete(key);
            //1.更新商品信息

            skuInfoService.update(null);

            //3.睡眠一段 500ms
            TimeUnit.MILLISECONDS.sleep(500);

            //4.再次清除缓存中数据
            redisTemplate.delete(key);
        } catch (InterruptedException e) {
            log.error("[商品服务]修改商品异常:{}", e);
            throw new RuntimeException(e);
        }
    }


    /**
     * 采用SpringDataRedis实现分布式
     * 查询商品信息避免缓存击穿引入分布式缓存+分布式锁
     *
     * @param skuId
     * @return
     */
    //@Override
    //public SkuInfo getSkuInfo(Long skuId) {
    //    try {
    //        //1.优先从分布式缓存中获取业务数据
    //        //1.1 拼接商品信息业务数据Key 形式: sku:商品skuId:info
    //        String dataKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
    //
    //        //1.2 获取业务数据
    //        SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(dataKey);
    //
    //        //2.命中缓存-直接返回业务数据即可
    //        if (skuInfo != null) {
    //            return skuInfo;
    //        }
    //        //3.未命中缓存-尝试获取分布式锁
    //        //3.1 拼接商品锁key 形式: "sku:商品skuId:lock"
    //        String lockKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKULOCK_SUFFIX;
    //        //3.2 生成每个线程锁的值 避免线程互相释放锁
    //        String lockValue = UUID.randomUUID().toString();
    //
    //        //3.3 尝试获取分布式锁
    //        Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS);
    //        if (flag) {
    //            try {
    //                //3.4 获取锁成功-执行查库业务  查库:结果判断
    //                skuInfo = this.getSkuInfoFromDB(skuId);
    //                //3.4.1 查询数据库有值 将查询结果放入分布式缓存Redis
    //                if (skuInfo != null) {
    //                    redisTemplate.opsForValue().set(dataKey, skuInfo, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
    //                    return skuInfo;
    //                } else {
    //                    //3.4.2 查询数据库无值 将空结果暂到放入分布式缓存Redis
    //                    redisTemplate.opsForValue().set(dataKey, skuInfo, 5, TimeUnit.MINUTES);
    //                    return skuInfo;
    //                }
    //            } finally {
    //                //采用lua脚本释放锁
    //                String scriptText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
    //                        "then\n" +
    //                        "    return redis.call(\"del\",KEYS[1])\n" +
    //                        "else\n" +
    //                        "    return 0\n" +
    //                        "end";
    //                DefaultRedisScript<Long> script = new DefaultRedisScript<>();
    //                script.setScriptText(scriptText);
    //                script.setResultType(Long.class);
    //                redisTemplate.execute(script, Arrays.asList(lockKey), lockValue);
    //            }
    //        } else {
    //            //3.5 获取锁失败-自旋(保证用户请求线程一定能获取到业务数据)
    //            TimeUnit.MILLISECONDS.sleep(500);
    //            return this.getSkuInfo(skuId);
    //        }
    //    } catch (Exception e) {
    //        //4.当锁服务不可以,兜底处理方案查询数据库
    //        log.error("[商品服务]获取商品信息锁服务不可用,兜底处理查库:{}", e);
    //        return this.getSkuInfoFromDB(skuId);
    //    }
    //}


    /**
     * 采用Redisson实现分布式锁
     * 查询商品信息避免缓存击穿引入分布式缓存+分布式锁
     *
     * @param skuId
     * @return
     */
    @Override
    public SkuInfo getSkuInfo(Long skuId) {
        //前置通知 开启事务
        try {
            //1.优先从分布式缓存Redis获取业务数据
            //1.1 拼接业务数据Key 形式 "sku:商品IDskuId:info"
            String dataKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
            //1.2 获取缓存中数据
            SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(dataKey);
            //2.命中缓存则直接响应业务数据
            if (skuInfo != null) {
                return skuInfo;
            }
            //3.未命中缓存则调用尝试获取分布式锁
            //3.1 拼接锁Key 形式 "sku:商品IDskuId:lock"
            String lockKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKULOCK_SUFFIX;
            //3.2 获取锁对象
            RLock lock = redissonClient.getLock(lockKey);
            //3.3 尝试获取分布锁
            boolean flag = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX2, RedisConst.SKULOCK_EXPIRE_PX1, TimeUnit.SECONDS);
            //5.获取锁成功 执行查库业务
            if (flag) {
                try {
                    skuInfo = this.getSkuInfoFromDB(skuId);
                    if (skuInfo != null) {
                        //查询数据库有值 将查询结果放入分布式缓存
                        redisTemplate.opsForValue().set(dataKey, skuInfo, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                        return skuInfo;
                    } else {
                        //查询数据库为空 出现缓存穿透
                        redisTemplate.opsForValue().set(dataKey, skuInfo, 5, TimeUnit.MINUTES);
                        return skuInfo;
                    }
                } finally {
                    //释放锁
                    lock.unlock();
                }
            } else {
                //6.获取锁失败 自旋(保证用户请求线程一定能获取到业务数据)
                TimeUnit.MILLISECONDS.sleep(500);
                return this.getSkuInfo(skuId);
            }
        } catch (Exception e) {
            //6.当锁服务不可以,兜底处理方案查询数据库
            log.error("[商品服务]获取商品信息锁服务不可用,兜底处理查库:{}", e);
            return this.getSkuInfoFromDB(skuId);
        }
        //后置同时
    }


    /**
     * 查询商品信息
     *
     * @param skuId
     * @return
     */
    @Override
    @GmallCache(prefix = RedisConst.SKUKEY_PREFIX, suffix = RedisConst.SKUKEY_SUFFIX)
    //调用数据库查询商品信息方法前执行前置通知,调用本方法后,执行后置通知
    public SkuInfo getSkuInfoFromDB(Long skuId) {
        //1.先根据ID查询商品对象
        SkuInfo skuInfo = skuInfoService.getById(skuId);
        //2.根据SkuID查询商品图片列表
        if (skuInfo != null) {
            LambdaQueryWrapper<SkuImage> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(SkuImage::getSkuId, skuId);
            List<SkuImage> skuImageList = skuImageService.list(queryWrapper);
            skuInfo.setSkuImageList(skuImageList);
        }
        return skuInfo;
    }


    @Autowired
    private BaseCategoryViewService baseCategoryViewService;

    /**
     * 根据三级分类ID获取分类信息
     *
     * @param category3Id
     * @return
     */
    @Override
    @GmallCache(prefix = "categoryView:", suffix = ":info")
    public BaseCategoryView getCategoryView(Long category3Id) {
        return baseCategoryViewService.getById(category3Id);
    }

    /**
     * 根据SKuID查询商品最新价格
     *
     * @param skuId
     * @return
     */
    @Override
    public BigDecimal getSkuPrice(Long skuId) {
        //TODO 加分布式锁控制查询价格方法 同一时间只能有一个进程调用
        LambdaQueryWrapper<SkuInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SkuInfo::getId, skuId);
        queryWrapper.select(SkuInfo::getPrice);
        SkuInfo skuInfo = skuInfoService.getOne(queryWrapper);
        if (skuInfo != null) {
            return skuInfo.getPrice();
        }
        return new BigDecimal("0.00");
    }

    /**
     * 根据skuId查询当前SKU商品包含平台属性列表(包含属性值)
     *
     * @param skuId
     * @return
     */
    @Override
    @GmallCache(prefix = "attrList:")
    public List<BaseAttrInfo> getAttrList(Long skuId) {
        //1.获取平台属性持久层对象 调用动态SQL
        SkuAttrValueMapper skuAttrValueMapper = (SkuAttrValueMapper) skuAttrValueService.getBaseMapper();
        return skuAttrValueMapper.getAttrList(skuId);
    }

    /**
     * 查询SPU商品所有销售属性,查询指定SKU销售属性选中效果
     *
     * @param skuId
     * @param spuId
     * @return
     */
    @Override
    @GmallCache(prefix = "spuSaleAttr:")
    public List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(Long skuId, Long spuId) {
        SpuSaleAttrMapper spuSaleAttrMapper = (SpuSaleAttrMapper) spuSaleAttrService.getBaseMapper();
        return spuSaleAttrMapper.getSpuSaleAttrListCheckBySku(skuId, spuId);
    }

    /**
     * 在一组SPU商品下 切换不同SKU字符串
     *
     * @param spuId "{'销售属性1|销售属性2':'sku商品ID','销售属性1|销售属性3':'sku商品ID'}"
     * @return
     */
    @Override
    @GmallCache(prefix = "skuValueIdsMap:")
    public String getSkuValueIdsMap(Long spuId) {
        SkuSaleAttrValueMapper skuSaleAttrValueMapper = (SkuSaleAttrValueMapper) skuSaleAttrValueService.getBaseMapper();
        //1.调用动态SQL得到多条 销售属性跟SKUID对照关系
        List<Map> list = skuSaleAttrValueMapper.getSkuValueIdsMap(spuId);
        //2.遍历List将所有销售属性ID作为jsonKey 商品SKUID作为val
        HashMap<Object, Object> mapResult = new HashMap<>();
        for (Map map : list) {
            Object value_ids = map.get("value_ids");
            Object skuId = map.get("sku_id");
            mapResult.put(value_ids, skuId);
        }
        return JSON.toJSONString(mapResult);
    }
}
